# -*- coding: utf-8 -*-
'''
Created on 23 nov. 2010

@author: thierry
'''

import os.path
import logging

import re
from sets import Set

from datetime import datetime

import vobject

import atom
import gdata.contacts
import gdata.contacts.service

import evolution.ebook



class GoogleAccount():
    
    # Regular expression to find all phone numbers in the vCard
    re_tel = re.compile('TEL;TYPE')

    fields_binding = [('','')]
    local_fields = ['name',
                    'family-name',
                    'given-name',
                    'full-name',
                    'nickname',
                    'email',
                    'email-1',
                    'email-2',
                    'email-3',
                    'email-4',
                    'mobile-phone',
                    'home-phone',
                    'company-phone',
                    'business-phone',
                    'other-phone',
                    'primary-phone',                   
                    'org',
                    'title',
                    'name-or-org']
    
    rel_phone_map = [(Set(['CELL','VOICE']),gdata.contacts.PHONE_MAIN,'mobile-phone'),
                           (Set(['HOME','CELL']), gdata.contacts.PHONE_MOBILE,'home-phone-2'),
                           (Set(['WORK','CELL']), gdata.contacts.PHONE_WORK_MOBILE,'business-phone-2'),
                           (Set(['VOICE']), gdata.contacts.PHONE_OTHER,'other-phone'),
                           (Set(['HOME','VOICE']), gdata.contacts.PHONE_HOME,'home-phone'),
                           (Set(['WORK','VOICE']), gdata.contacts.PHONE_WORK, 'business-phone'),
                           (Set(['FAX']), gdata.contacts.PHONE_FAX, 'other-fax-phone')                           
                           ]  
    rel_mail_map = {'WORK': gdata.contacts.REL_WORK,
                           'HOME': gdata.contacts.REL_HOME,
                           }     

    remoteContacts = None
    localContacts = None
    

   

    def __init__(self, login, password):        
        self.gd_client_for_creation = self._create_gdata_client(login, password)
        # specify v3 is required tom ake birthday, nikname, url work...
        self.gd_client = self._create_gdata_client(login, password, force_v3=True)
        self._ensure_storage_home_exist()
        
    def _create_gdata_client(self, login, password, force_v3=False):
        if force_v3:            
            gd_client = gdata.contacts.service.ContactsService(additional_headers={gdata.contacts.service.GDATA_VER_HEADER:3})
        else:            
            gd_client = gdata.contacts.service.ContactsService()
        gd_client.email = login
        gd_client.password = password
        gd_client.source = "bressure.net-bookle-1.0"
        try:
            gd_client.ProgrammaticLogin()
        except gdata.service.BadAuthentication:
            logging.exception("failed to log in gmail")
            raise
        return gd_client
    
    def synchronize(self, fullSync=False):
        """
        synchronize local and google address book.
        N900 address book away override Google contacts but contact that does not existe 
        in N900 will be downloaded from google. 
        """
        logging.info("start syncing")
        uc = self.commitContacts(fullSync=fullSync)
        dc = self.updateContacts()
        logging.info("syncing finished (up:%d,down:%d)" % (uc,dc))



    def _get_nookle_storage_dir(self):
        '''
        Compute the file  system storage for nookle stuff
        '''
        storage = os.path.expanduser("~")
        storage = os.path.join(storage, ".nookle")
        return storage   
    
    def _get_storage_home(self, a_storage="last_sync"):
        storage = os.path.join(self._get_nookle_storage_dir(), a_storage)
        return storage


    def _ensure_storage_home_exist(self, a_storage="last_sync"):
        storage = self._get_storage_home(a_storage=a_storage)
        if not os.path.exists(storage):
            os.makedirs(storage)        
        return storage



    def _getSyncFile(self, v):
        storage = self._get_storage_home()                        
        sync_file = os.path.join(storage, v.uid.value)
        return sync_file        
        
    def _markAsSynced(self,v):
        sync_file = self._getSyncFile(v)
        f = open(sync_file,"w")
        f.write("%s\n" % v.rev.value)
        f.close()


    def _localModifiedSinceLastSync(self, v):
        FORMAT_REV_N900 = "%Y-%m-%dT%H:%M:%S"
        sync_file = self._getSyncFile(v)        
        if os.path.exists(sync_file):
            # has been synced
            f = open(sync_file,"r")
            last_rev = datetime.strptime(f.readline().partition("Z")[0], FORMAT_REV_N900)            
            current_rev = datetime.strptime(v.rev.value.partition("Z")[0],FORMAT_REV_N900)
            return current_rev > last_rev
        else:
            # never synced
            return True
        
        
    def commitContacts(self, fullSync=False):
        """
        Upload local contacts into google address book
        """
        logging.info("uploading local contacts ==> Google")
        contacts = self.getAllLocalContacts()
        count = 0        
        for contact in contacts:
            logging.debug("uid: %s , %s" % (contact.get_uid(), contact.get_name()))
            entry =  self.getRemoteContact(contact)
            if entry :
                vcard = contact.get_vcard_string()
                v = vobject.readOne(vcard)                                
                if self._localModifiedSinceLastSync(v) or fullSync:
                    logging.info("uid: %s, %s  modified or new, syncing now" % (contact.get_uid(), contact.get_name()))
                    self.loadLocalToRemote(contact, entry)
                    self._markAsSynced(v)
                    count += 1
                else:
                    logging.debug("not modified since last sync")
        return count

    
    def createLocalContactIfNew(self, entry, reload=False):
        """
        Create a new local contact for a google contact.         
        """
        result = None
        if entry.title and entry.title.text and len(entry.title.text.strip()) > 0 :
            logging.debug("looking for local contact with name '%s'" % entry.title.text)
            for econtact in self.getAllLocalContacts(reload=reload):
                if econtact.get_name() :
                    if econtact.get_name().upper() == entry.title.text.upper():
                        # this contact already exist and must not be handle
                        # cause google MUST NOT erase any N900 data !!!!
                        logging.debug("already in N900, pass")
                        break;
            else:
                # no match found so this is a new contact
                logging.debug("no match found, create a new one")
                abook = evolution.ebook.open_addressbook("default")
                econtact = evolution.ebook.EContact()
                self.loadRemoteToLocal(entry, econtact)
                abook.add_contact(econtact)
                logging.info("%s created on N900" % entry.title.text)
                result = econtact

                                                                               
        else:
            # no march found
            logging.debug("contact with no title is unsupported")

        return result        
    
    
    def loadRemoteToLocal(self, entry, econtact):
        """
        Read all suitable value from a google contact then write them into a evolution contact
        """
        logging.debug("fullname : %s" % entry.title.text)   
        name = entry.title.text.split()
        if len(name) > 0 :
            family_name = ""
            for n in range(1,len(name)):
                if n > 1:
                    family_name += " "
                family_name += name[n]
            econtact.set_property("family-name", family_name)
            econtact.set_property("given-name", name[0])         
        else:
            logging.warn("cannot compute a name for the contact") 
        if entry.nickname :               
            logging.debug("nickname : %s" % entry.nickname.text)        
            econtact.set_property("nickname", entry.nickname.text)
        if entry.content :
            logging.debug("note : %s" % entry.content.text)        
            econtact.set_property("note", entry.content.text)
        if entry.website :
            logging.debug("homepage-url : %s" % entry.website[0].href)
            econtact.set_property("homepage-url", entry.website[0].href)
#        # this does not work
#        logging.debug("birth-date : %s" % entry.birthday.when)            
#        econtact.set_property("birth-date", entry.birthday.when)
        if entry.organization :
            if entry.organization.org_name :
                logging.debug("org : %s" % entry.organization.org_name.text)
                econtact.set_property("org", entry.organization.org_name.text)
            if entry.organization.org_title :            
                logging.debug("title : %s" %  entry.organization.org_title.text )
                econtact.set_property("title",  entry.organization.org_title.text)
        
#        # this does not work
#        for email in entry.email:
#            logging.debug("%s (%s)" % (email.address,email.rel))
#            econtact.set_property("email", email.address)
        if entry.phone_number :   
            for phone_number in entry.phone_number:
                logging.debug("phone : %s (%s)" % (phone_number.text, phone_number.rel))
                for paramset,rel_google, evo_prop in self.rel_phone_map:
                    if rel_google == phone_number.rel:
                        econtact.set_property(evo_prop, phone_number.text)
 
    
    def loadLocalToRemote(self, econtact, entry):
        """
        read all suitable value from econtact and write them in google entry
        """
        if not entry.title:
            entry.title = atom.Title()
        entry.title.text = econtact.get_name()     
           
        vcard = econtact.get_vcard_string()
        v = vobject.readOne(vcard)             

        if not entry.updated:
            entry.updated = atom.Content()
        entry.updated.text = v.rev.value
        
        try:
            logging.debug("note: %s" % v.note.value)
        except AttributeError:
            pass        
        else:
            if not entry.content:
                entry.content = atom.Content()
            entry.content.text = v.note.value
        
        try:
            logging.debug("nickname: %s" % v.nickname.value)
        except AttributeError:
            pass
        else:    
            if not entry.nickname:
                entry.nickname = gdata.contacts.Nickname()        
            entry.nickname.text = v.nickname.value
        
        try:
            logging.debug("url: %s" % v.url)
        except AttributeError:
            pass
        else:                
            entry.website[:] = []                
            entry.website.append(gdata.contacts.Website(href=v.url.value, primary='true', label='URL'))
        
        try:
            logging.debug("birthday: %s" % v.bday.value)
        except AttributeError:
            pass
        else:                                    
            if not entry.birthday:
                entry.birthday = gdata.contacts.Birthday()             
            entry.birthday.when = v.bday.value 
            
        try:    
            logging.debug("org: %s" % v.org.value)
        except AttributeError:
            pass
        else:
            if not entry.organization:
                entry.organization = gdata.contacts.Organization()
            entry.organization.org_name = gdata.contacts.OrgName(text=v.org.value[0])                      
        
        try:
            logging.debug("title: %s" % v.title.value)
        except AttributeError:
            pass
        else:
            if not entry.organization:
                entry.organization = gdata.contacts.Organization()
            entry.organization.org_title = gdata.contacts.OrgTitle(text=v.title.value)
        
        entry.email[:] = []
        try:
            v.email_list
        except AttributeError:
            pass
        else:
            for email in v.email_list:            
                address = email.value 
                is_primary = 'false'
                rel_value = None
                try:
                    types = email.type_param
                except AttributeError:
                    types = ""                
                else:                        
                    if types in self.rel_mail_map.keys():
                        rel_value = self.rel_mail_map[types]
                    else:
                        rel_value = gdata.contacts.REL_OTHER            
                logging.debug("email[%s] : %s" % (types,address))
                if rel_value:
                    entry.email.append(gdata.contacts.Email(address=address,
                                    primary=is_primary, rel= rel_value))
                else:
                    entry.email.append(gdata.contacts.Email(address=address,
                                    primary=is_primary))
                
        entry.phone_number[:] = []
        try:
            v.tel_list
        except AttributeError:
            pass
        else:
            for tel in v.tel_list:            
                phone = tel.value 
                is_primary = 'false'
                rel_value = None
                try:
                    types = tel.type_paramlist
                except AttributeError:
                    types = ""                
                else:                
                    types_set = Set(types)
                    for paramset,rel_google, evo_prop in self.rel_phone_map:
                        if len(paramset.symmetric_difference(types_set)) == 0 :
                            rel_value = rel_google
                            break                
                    else:
                        rel_value = gdata.contacts.REL_OTHER            
                logging.debug("phone[%s] : %s" % (types,phone))
                if rel_value:
                    entry.phone_number.append(gdata.contacts.PhoneNumber(text=phone,
                                    primary=is_primary, rel= rel_value))
                else:
                    entry.phone_number.append(gdata.contacts.PhoneNumber(text=phone,
                                    primary=is_primary))
                                                   
        

        self.gd_client.UpdateContact(entry.GetEditLink().href, entry)
    
        
    def getRemoteContact(self,econtact, reload=False):
        """
        Return the remote entry for a N900 contact. Create a new one if needed.
        """
        result = None
        if econtact.get_name() and len(econtact.get_name().strip()) > 0  :
            logging.debug("looking for remote contact with name '%s'" % econtact.get_name())
            for entry in self.getAllRemoteContacts(reload=reload):
                if entry.title.text : 
                    if econtact.get_name().upper() == entry.title.text.upper():
                        result = entry
                        logging.debug("found!")
                        break;
            else:
                # no match found
                logging.debug("no match found, create a new one")
                new_contact = gdata.contacts.ContactEntry(title=atom.Title(text=econtact.get_name()))
                # Create a work email address for the contact and use as primary.
                new_contact.email.append(gdata.contacts.Email(address="toto@titi.com",
                                                              primary='true', rel=gdata.contacts.REL_WORK))

                # Send the contact data to the server.
                result = self.gd_client_for_creation.CreateContact(new_contact)
                logging.info(" %s created in google " % econtact.get_name())
                # this result is labeled v1 so we must search again with v3 api
                # we reset the cache because the newly contact is not in the cache 
                result = self.getRemoteContact(econtact, reload=True)
                
               
                
        else:
            # no march found
            logging.debug("contact with no name is NOT supported")

        return result
        
        
    def getAllLocalContacts(self, reload=False):
        """
        Return the all local contact
        """
        if not self.localContacts or reload :
            abook = evolution.ebook.open_addressbook("default")
            self.localContacts = abook.get_all_contacts()
        return self.localContacts
    
    def getAllRemoteContacts(self, reload=False):
        """
        Return all remote entry.
        """
        if not self.remoteContacts or reload :
            query = gdata.contacts.service.ContactsQuery()
            query.max_results = 10000        
            self.remoteContacts = self.gd_client.GetContactsFeed(query.ToUri()).entry
        return self.remoteContacts


        
    
    def updateContacts(self):
        """
        Download google address book into local contacts
        """
        logging.info("downloading remote contacts <== Google")        
        allContacts = self.getAllRemoteContacts()
        count = 0        
        for entry in allContacts:
            # chekc if this contact is already on local address book
            logging.debug(entry.title.text)        
            if self.createLocalContactIfNew(entry):
                count += 1
        return count
